﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Terminal.Advanced_AEUC_settings_controls.Scripting;
using System.IO;
using Auxiliaries;

namespace Terminal.Advanced_AEUC_settings_controls.Scripting
{
    partial class ScrEditBox : UserControl
    {
        ScrMainModule scrEngine;
        delegate void EmptyDelegate();
        delegate void LoadFile(FileInfo finf);
        delegate void LoadText(string script);
        /// <summary>
        /// Delegate used for some event declarations
        /// </summary>
        /// <param name="text">String argument</param>
        public delegate void UIUpdateDelegate(string text);
        /// <summary>
        /// Event fired when the text box selection/caret changes to a different line
        /// </summary>
        public event UIUpdateDelegate OnLineChange;
        public event UIUpdateDelegate OnFileSaved;
        FileInfo crntScriptFile = null;
        int crntCaretLine = -1;
        bool fileSaved = false;
        public bool executing = false;
        #region Context menu items
        #region Exec items
        const string CTX_MENU_ADD_BKP = "Add break point";
        const string CTX_MENU_REM_BKP = "Remove break point";
        const string CTX_MENU_RUN_2_CUR = "Run exec to line";
        const string CTX_MENU_SET_PC_2_CUR = "Set current exec line";
        MenuItem miAddBkp;
        MenuItem miRemBkp;
        MenuItem miRun2Cur;
        MenuItem miSet2Cur;
        #endregion
        #region Edit items
        //const string CTX_MENU_RFRSH = "Refresh parse";
        const string CTX_MENU_SAVE = "File save";
        const string CTX_MENU_LOAD = "File load";
        #endregion
        #endregion

        public ScrEditBox()
        {
            InitializeComponent();

            #region Context menu initialization
            #region Exec context menu
            ContextMenu = new ContextMenu();
            ContextMenu.Popup += new EventHandler(ContextMenu_Popup);
            EventHandler ehExec = new EventHandler(ContexMenuHandlerExec);
            miAddBkp = new MenuItem(CTX_MENU_ADD_BKP, ehExec);
            miRemBkp = new MenuItem(CTX_MENU_REM_BKP, ehExec);
            miRun2Cur = new MenuItem(CTX_MENU_RUN_2_CUR, ehExec);
            miSet2Cur = new MenuItem(CTX_MENU_SET_PC_2_CUR, ehExec);

            ContextMenu.MenuItems.Add(miRun2Cur);
            ContextMenu.MenuItems.Add(miSet2Cur);
            #endregion
            #region Edit context menu
            rtMainEdit.ContextMenuStrip = new ContextMenuStrip();
            EventHandler ehEdit = new EventHandler(ContextMenuHandlerEdit);

            //rtMainEdit.ContextMenuStrip.Items.Add(CTX_MENU_RFRSH, null, ehEdit);
            rtMainEdit.ContextMenuStrip.Items.Add(CTX_MENU_SAVE, null, ehEdit);
            rtMainEdit.ContextMenuStrip.Items.Add(CTX_MENU_LOAD, null, ehEdit);
            #endregion
            #endregion

            rtMainEdit.VScroll += new EventHandler(rtMainEdit_VScroll);
            rtMainEdit.WordWrap = false;

            rtMainEdit.KeyDown += new KeyEventHandler(ScrEditBox_KeyDown);
            rtMainEdit.SelectionChanged += new EventHandler(rtMainEdit_SelectionChanged);
        }

        #region Context menu handlers - exec options
        /// <summary>
        /// Stores the last context menu associated rtMainEdit line for the context menu handler function to use
        /// </summary>
        int ctxMenuLnPos = -1;
        /// <summary>
        /// Creates the context menu based on the line that it is clicked on
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ContextMenu_Popup(object sender, EventArgs e)
        {
            if (scrEngine != null)
            {
                ContextMenu.MenuItems.Clear();
                ctxMenuLnPos = rtMainEdit.GetLineFromCharIndex(rtMainEdit.GetCharIndexFromPosition(PointToClient(MousePosition)));

                if (scrEngine.CheckBreakPointHit(ctxMenuLnPos))
                    ContextMenu.MenuItems.Add(miRemBkp);
                else
                    ContextMenu.MenuItems.Add(miAddBkp);
                if (scrEngine.GetCrntExecLine() > -1)
                {
                    if (ctxMenuLnPos != scrEngine.GetCrntExecLine())
                    {
                        ContextMenu.MenuItems.Add(miSet2Cur);
                        if (ctxMenuLnPos > scrEngine.GetCrntExecLine())
                            ContextMenu.MenuItems.Add(miRun2Cur);
                    }
                }
            }
        }
        /// <summary>
        /// Handler for the context menu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ea"></param>
        void ContexMenuHandlerExec(object sender, EventArgs ea)
        {
            MenuItem tmpItem = (MenuItem)sender;
            scrEngine.SetActiveCmdLines(rtMainEdit.Lines.ToArray());

            if (tmpItem.Text == CTX_MENU_ADD_BKP)
            {
                scrEngine.AddBreakPoint(ctxMenuLnPos);
                Invalidate();
            }
            else if (tmpItem.Text == CTX_MENU_REM_BKP)
            {
                scrEngine.RemBreakPoint(ctxMenuLnPos);
                Invalidate();
            }
            else if (tmpItem.Text == CTX_MENU_RUN_2_CUR)
            {
                scrEngine.RunExecToLine(ctxMenuLnPos);
            }
            else if (tmpItem.Text == CTX_MENU_SET_PC_2_CUR)
            {
                scrEngine.SetExecToLine(ctxMenuLnPos);
            }
        }
        #endregion
        #region Context menu handlers - edit options
        private void ContextMenuHandlerEdit(object sender, EventArgs ea)
        {
            ToolStripMenuItem tmpItem = (ToolStripMenuItem)sender;

            try
            {
                if (tmpItem.Text == CTX_MENU_SAVE)
                {
                    SaveFileDialog sfd = new SaveFileDialog();
                    sfd.Filter = "Script files (*.scp)|*.scp|All files (*.*)|*.*";

                    if (sfd.ShowDialog() == DialogResult.OK)
                    {
                        SaveScriptFile(new FileInfo(sfd.FileName));
                    }
                }
                else if (tmpItem.Text == CTX_MENU_LOAD)
                {
                    OpenFileDialog ofd = new OpenFileDialog();
                    ofd.Filter = "Script files (*.scp)|*.scp|All files (*.*)|*.*";

                    if (ofd.ShowDialog() == DialogResult.OK)
                    {
                        LoadScriptFile(new FileInfo(ofd.FileName));
                    }
                }
                /*else if (tmpItem.Text == CTX_MENU_RFRSH)
                {
                    scrEngine.SetActiveCmdLines(rtMainEdit.Lines.ToArray());
                }
                */
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion

        /// <summary>
        /// Loads the provided script command
        /// </summary>
        /// <param name="scrCmd">String containing script command</param>
        public void LoadScriptText(string scrCmd)
        {
            if (InvokeRequired)
            {
                Invoke(new LoadText(LoadScriptText), new object[] { scrCmd });
            }
            else
            {
                rtMainEdit.Text = scrCmd;

                rtMainEdit.SelectionStart = 0;
                rtMainEdit.ScrollToCaret();

                crntScriptFile = null;
            }
        }
        /// <summary>
        /// Loads the specified script file
        /// </summary>
        /// <param name="scrFile">Specified script file</param>
        public void LoadScriptFile(FileInfo scrFile)
        {
            if (InvokeRequired)
            {
                Invoke(new LoadFile(LoadScriptFile), new object[] { scrFile });
            }
            else
            {
                TextReader txRd = new StreamReader(scrFile.OpenRead());
                rtMainEdit.Text = txRd.ReadToEnd();
                txRd.Close();

                rtMainEdit.SelectionStart = 0;
                rtMainEdit.ScrollToCaret();

                crntScriptFile = scrFile;

                fileSaved = true;
                if (OnFileSaved != null)
                    OnFileSaved(crntScriptFile.FullName + " opened");
            }
        }
        /// <summary>
        /// Saves the specified script file
        /// </summary>
        /// <param name="scrFile">Specified script file</param>
        public void SaveScriptFile(FileInfo scrFile)
        {
            TextWriter txWr = new StreamWriter(scrFile.Create());
            txWr.Write(rtMainEdit.Text);
            txWr.Close();

            crntScriptFile = scrFile;

            fileSaved = true;
            if (OnFileSaved != null)
                OnFileSaved(crntScriptFile.FullName + " saved");
        }
        /// <summary>
        /// Executes the current script in separate thread context
        /// </summary>
        public void ScriptExecuteThreaded()
        {
            if (InvokeRequired)
            {
                Invoke(new EmptyDelegate(ScriptExecuteThreaded));
            }
            else
            {
                string fName = "";
                if (crntScriptFile != null)
                    fName = crntScriptFile.Name;
                executing = true;
                scrEngine.Execute(rtMainEdit.Lines, ScrRunTimeEnv.RunMode.FREE_RUN, fName, true, 0);
            }
        }
        /// <summary>
        /// Executes the current script in the current thread cotnext
        /// </summary>
        public void ScriptExecute(int cmdLineStart)
        {
            //if (InvokeRequired)
            //{
            //    Invoke(new EmptyDelegate(ScriptExecute));
            //}
            //else
            //{
                string fName = "";
                if (crntScriptFile != null)
                    fName = crntScriptFile.Name;
                executing = true;
                scrEngine.Execute(rtMainEdit.Lines, ScrRunTimeEnv.RunMode.FREE_RUN, fName, false, cmdLineStart);
            //}
        }
        /// <summary>
        /// Generates source code from the current script
        /// </summary>
        public void ScriptSourcesify(string modName)
        {
            if (InvokeRequired)
            {
                Invoke(new UIUpdateDelegate(ScriptSourcesify), new object[] { modName });
            }
            else
            {
                scrEngine.Sourcesify(modName, rtMainEdit.Lines, ScrRunTimeEnv.RunMode.FREE_RUN);
            }
        }
        /// <summary>
        /// Get the generated C source
        /// </summary>
        /// <returns>String containing the generated C source</returns>
        public string ScriptCSourceGet()
        {
            return scrEngine.components.rteCmn.c_source;
        }
        /// <summary>
        /// Message-header source generation function
        /// </summary>
        /// <returns>Definition and initialization of source messages</returns>>
        public string ScriptMSGSourceGet()
        {
            return scrEngine.generateMessageSource();
        }
        /// <summary>
        /// Returns the last script status or 0 if there was no status set
        /// </summary>
        public uint ScriptGetLastError()
        {
            if (scrEngine.components.rteCmn.rteResult != null)
            {
                try
                {
                    return (uint)scrEngine.components.rteCmn.rteResult;
                }
                catch
                {
                    return 0;
                }
            }
            else
            {
                return 0;
            }
        }

        #region Script engine generated events and registering

        public void SetBoundScrEngine(ScrMainModule iScrEngine)
        {
            scrEngine = iScrEngine;
            
            scrEngine.components.rteCmn.OnScriptDone += new ScrMainModule.ScriptEvent(scrEngine_OnScriptDone);
            scrEngine.components.rteCmn.OnScriptBreakReached += new ScrMainModule.ScriptEvent(scrEngine_OnScriptBreakReached);
            scrEngine.components.rteCmn.OnScriptExecLineChange += new ScrMainModule.ScriptEvent(scrEngine_OnScriptExecLineChange);
        }

        void scrEngine_OnScriptExecLineChange(string ErrorStatus)
        {
            Invalidate();
        }

        void scrEngine_OnScriptBreakReached(string ErrorStatus)
        {
            SetButtonText("Go");
            Invalidate();
        }

        void scrEngine_OnScriptDone(string ErrorStatus)
        {
            executing = false;
            SetButtonText("Go");
            Invalidate();
        }

        #endregion

        #region Script text editting field UI

        void rtMainEdit_SelectionChanged(object sender, EventArgs e)
        {
            if (crntCaretLine != rtMainEdit.GetLineFromCharIndex(rtMainEdit.SelectionStart))
            {
                crntCaretLine = rtMainEdit.GetLineFromCharIndex(rtMainEdit.SelectionStart);
                if (OnLineChange != null)
                    OnLineChange(crntCaretLine.ToString());
            }
        }

        private void ScrEditBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Control)
            {
                if (e.KeyCode == Keys.G)
                {
                    AuxQueryBox subCrtForm = new AuxQueryBox(AuxQueryBox.IfModeSel.Go_To_Line, rtMainEdit.GetLineFromCharIndex(rtMainEdit.SelectionStart).ToString());
                    if (subCrtForm.ShowDialog() == DialogResult.OK)
                    {
                        int ln;
                        if (int.TryParse(subCrtForm.GetInputText(), out ln))
                        {
                            rtMainEdit.SelectionStart = rtMainEdit.GetFirstCharIndexFromLine(ln);
                            rtMainEdit.ScrollToCaret();
                        }
                    }
                }
                else if (e.KeyCode == Keys.S)
                {
                    if (crntScriptFile == null)
                    {
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.Filter = "Script files (*.scp)|*.scp|All files (*.*)|*.*";

                        if (sfd.ShowDialog() == DialogResult.OK)
                        {
                            crntScriptFile = new FileInfo(sfd.FileName);
                        }
                    }

                    if (crntScriptFile != null)
                    {
                        SaveScriptFile(crntScriptFile);
                    }
                }
                else if (e.KeyCode == Keys.O)
                {
                    OpenFileDialog ofd = new OpenFileDialog();
                    ofd.Filter = "Script files (*.scp)|*.scp|All files (*.*)|*.*";

                    if (ofd.ShowDialog() == DialogResult.OK)
                    {
                        LoadScriptFile(new FileInfo(ofd.FileName));
                    }
                }
            }
            else
            {
                if (fileSaved)
                {
                    fileSaved = false;
                    if (OnFileSaved != null)
                    {
                        if(crntScriptFile != null)
                            OnFileSaved(crntScriptFile.FullName + " changed");
                    }
                }
                scrEngine.SetActiveCmdLines(rtMainEdit.Lines.ToArray());
            }
        }

        private void ScrEditBox_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (scrEngine != null)
            {
                scrEngine.SetActiveCmdLines(rtMainEdit.Lines.ToArray());

                Point propHit = new Point(0, e.Y);
                int brkPos = rtMainEdit.GetLineFromCharIndex(rtMainEdit.GetCharIndexFromPosition(propHit));
                if (scrEngine.CheckBreakPointHit(brkPos))
                {
                    scrEngine.RemBreakPoint(brkPos);
                }
                else
                {
                    scrEngine.AddBreakPoint(brkPos);
                }

                Invalidate();
            }
        }

        Point rtbCmdLineLastCoors = new Point(0, 0);

        private void rtMainEdit_MouseMove(object sender, MouseEventArgs e)
        {
            rtbCmdLineLastCoors = e.Location;

            if (e.X > rtMainEdit.Size.Width / 2 && e.Y > rtMainEdit.Size.Height * 2 / 3)
            {
                if (!btnScriptGo.Visible)
                {
                    btnScriptGo.Show();
                    btnScriptStep.Show();
                }
            }
            else
            {
                btnScriptGo.Hide();
                btnScriptStep.Hide();
            }
        }

        private void rtMainEdit_MouseLeave(object sender, EventArgs e)
        {
            if (!(rtbCmdLineLastCoors.X > rtMainEdit.Size.Width / 2 &&
                rtbCmdLineLastCoors.X < rtMainEdit.Size.Width &&
                rtbCmdLineLastCoors.Y > rtMainEdit.Size.Height * 2 / 3 &&
                rtbCmdLineLastCoors.Y < rtMainEdit.Size.Height))
            {
                btnScriptGo.Hide();
                btnScriptStep.Hide();
            }
        }

        private void btnScriptGo_Click(object sender, EventArgs e)
        {
            try
            {
                if (scrEngine.GetCrntExecLine() == -1)
                {
                    btnScriptGo.Text = "Halt";
                    string fName = "";
                    if (crntScriptFile != null)
                        fName = crntScriptFile.Name;
                    scrEngine.Execute(rtMainEdit.Lines, ScrRunTimeEnv.RunMode.FREE_RUN, fName, true, 0);                    

                }
                else
                {
                    scrEngine.SetActiveCmdLines(rtMainEdit.Lines.ToArray());
                    scrEngine.SetScriptRun();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnScriptStep_Click(object sender, EventArgs e)
        {
            try
            {
                if (scrEngine.GetCrntExecLine() == -1)
                {
                    string fName = "";
                    if (crntScriptFile != null)
                        fName = crntScriptFile.Name;
                    scrEngine.Execute(rtMainEdit.Lines, ScrRunTimeEnv.RunMode.STEP_RUN, fName, true, 0);
                }
                else
                {
                    scrEngine.SetActiveCmdLines(rtMainEdit.Lines.ToArray());
                    scrEngine.SetScriptStep();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        void SetButtonText(string text)
        {
            if (InvokeRequired)
            {
                Invoke(new UIUpdateDelegate(SetButtonText), new object[] { text });
            }
            else
            {
                btnScriptGo.Text = text;
            }
        }

        #endregion

        #region Special symbols drawing and invalidating
        /// <summary>
        /// Draws the arrow showing the currently active line
        /// </summary>
        /// <param name="startP">Left top corner of the arrow</param>
        /// <param name="scale">Magnitude of the arrow</param>
        /// <returns>Graphics path arror representation</returns>
        GraphicsPath DoArrow(Point startP, float scale)
        {
            byte[] pthPtType = new byte[8];
            PointF[] arrow = new PointF[8];

            arrow[0] = new PointF(startP.X, startP.Y + 3*scale);
            pthPtType[0] = (byte)PathPointType.Start;

            arrow[1] = new PointF(startP.X + 4 * scale, startP.Y + 3 * scale);
            pthPtType[1] = (byte)PathPointType.Line;

            arrow[2] = new PointF(startP.X + 4 * scale, startP.Y);
            pthPtType[2] = (byte)PathPointType.Line;

            arrow[3] = new PointF(startP.X + 8 * scale, startP.Y + 5 * scale);
            pthPtType[3] = (byte)PathPointType.Line;

            arrow[4] = new PointF(startP.X + 4 * scale, startP.Y + 10 * scale);
            pthPtType[4] = (byte)PathPointType.Line;

            arrow[5] = new PointF(startP.X + 4 * scale, startP.Y + 7 * scale);
            pthPtType[5] = (byte)PathPointType.Line;

            arrow[6] = new PointF(startP.X, startP.Y + 7 * scale);
            pthPtType[6] = (byte)PathPointType.Line;

            arrow[7] = new PointF(startP.X, startP.Y + 3 * scale);
            pthPtType[7] = (byte)PathPointType.Line;

            return new GraphicsPath(arrow, pthPtType);
        }
        /// <summary>
        /// Draws the red circles associated with breakpoints and the current executing line arrow
        /// </summary>
        /// <param name="gfx">The control(event) associated graphics object</param>
        void drawActLineBreakCircles(Graphics gfx)
        {
            int[] breaks = scrEngine.GetBreakPoints();
            int activeLine = scrEngine.GetCrntExecLine();

            if (breaks != null)
            {
                for (int i = 0; i < breaks.Length; i++)
                {
                    Point lnStartP = rtMainEdit.GetPositionFromCharIndex(rtMainEdit.GetFirstCharIndexFromLine(breaks[i]));
                    if (breaks[i] < rtMainEdit.Lines.Length)
                    {
                        SizeF lineSize = gfx.MeasureString(rtMainEdit.Lines[breaks[i]], Font);
                        Rectangle brkCrcl = new Rectangle();
                        brkCrcl.Location = new Point(rtMainEdit.Location.X - (int)lineSize.Height - 2, rtMainEdit.Location.Y + lnStartP.Y);
                        brkCrcl.Size = new Size((int)lineSize.Height, (int)lineSize.Height);
                        gfx.FillEllipse(Brushes.Maroon, brkCrcl);
                    }
                }
            }

            if (activeLine > -1)
            {
                Point lnStartP = rtMainEdit.GetPositionFromCharIndex(rtMainEdit.GetFirstCharIndexFromLine(activeLine));
                if (activeLine < rtMainEdit.Lines.Length)
                {
                    SizeF lineSize = gfx.MeasureString(rtMainEdit.Lines[activeLine], Font);
                    Point actLn = new Point( 2, rtMainEdit.Location.Y + lnStartP.Y);
                    gfx.FillPath(Brushes.Yellow, DoArrow(actLn, 1.5f));
                    gfx.DrawPath(new Pen(Brushes.Black), DoArrow(actLn, 1.5f));

                    Point crntLine = rtMainEdit.GetPositionFromCharIndex(rtMainEdit.GetFirstCharIndexOfCurrentLine());
                    if (lnStartP.Y - crntLine.Y > rtMainEdit.Size.Height)
                    {
                        rtMainEdit.SelectionStart = rtMainEdit.GetFirstCharIndexFromLine(activeLine);
                        rtMainEdit.ScrollToCaret();
                    }
                }
            }
        }
        /// <summary>
        /// Overrided OnPaint event to include the drawing of the scriptspecial objects
        /// </summary>
        /// <param name="e">Paint event parameters</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            drawActLineBreakCircles(e.Graphics);
            base.OnPaint(e);
        }
        /// <summary>
        /// When the inline rich textbox fires the horizontal scroll event, redraw surface as objects relative positions have changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void rtMainEdit_VScroll(object sender, EventArgs e)
        {
            Invalidate();
        }
        #endregion
    }
}
